package fr.lip6.meta.ple.featureIdentification;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;

import fr.lip6.meta.tools.Trigger;

/** The class Feature represents a list of triggers. 
 * @author Luz
 */
public class Feature extends ArrayList<Trigger> {

	/**
	 * For avoiding warnings
	 */
	private static final long serialVersionUID = 1L;
	
	/**
	 * The product identifiers
	 */
	protected LinkedHashSet<Integer> prodIds = new LinkedHashSet<Integer>();
	
	/**
	 * Optional id
	 */
	protected String id;
	/**
	 * Optional name
	 */
	protected String name;


	/**
	 * Constructor
	 */
	public Feature() {
		super();
	}
	
	/**
	 * Constructor
	 * @param triggers - the collection of triggers
	 */
	public Feature(Collection<Trigger> triggers) {
		super(triggers);
	}
	
	/**
	 * Returns the feature representing the node
	 * @param node - the feature node
	 * @return an equivalent feature, without the children information
	 */
	public static Feature featureFromNode(FeatureNode node) {
		Feature f = new Feature(node);
		f.prodIds = node.prodIds;
		return f;
	}

	/**
	 * Returns the feature representing a product
	 * and adds its id
	 * @param p - the product
	 * @return an equivalent feature
	 */
	public static Feature featureFromProduct(Product p) {
		Feature f = new Feature(p);
		f.prodIds.add(p.getId());
		return f;
	}
	
	public static Feature extractIntersection(List<Product> products) {
		if (products.isEmpty()) return new Feature();
		
		//Intersection
		// on a la liste transition du premier produit
		Feature f = featureFromProduct(products.get(0));
		for (int i=1; i < products.size(); i++) {
			// on recupere les produit suivant
			Product p = products.get(i);
			// on garde que les trigger en commun
			f.retainAll(p);
			// on ajoute le num produit 
			f.prodIds.add(p.getId());
		}

		//Extraction
		for (Product p : products) {
			// on enleve les trigger que on a trouver dans lintersection 
			p.removeAll(f);
		}
		return f;
	}

	
	/**
	 * Product ids set getter
	 * @return
	 */
	public LinkedHashSet<Integer> getProdIds() {
		return prodIds;
	}
	
	/**
	 * ID getter
	 * @return
	 */
	public String getId() {
		return id;
	}

	/**
	 * ID setter
	 * @param id
	 */
	public void setId(String id) {
		this.id = id;
	}
	
	/**
	 * Name setter
	 * @param id
	 */
	public void setName(String name) {
		this.name = name;
	}
	public String getName() {
		return name;
	}
	// A ameliorer avec lutilisation dun StringBuffer
	/**
	 * String-representation with a given indentation
	 * @param leng - the size of the indentation
	 * @return the string
	 */
	public String toStringLines(int leng) {
		String s = "";
		String ind = "";
		for (int i=0; i<leng; i++) {
			ind += "  ";
		}
		if (isEmpty()) return s;
		for (int i = 0; i < size() - 1; i++) {
			s += ind + get(i) + "\n";
		}
		s += ind + get(size() - 1);
		return s;
	}
	
	public String toString() {
		return prodIds + ": " + super.toString();
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		result = prime * result + ((prodIds == null) ? 0 : prodIds.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (!super.equals(obj))
			return false;
		if (!(obj instanceof Feature))
			return false;
		Feature other = (Feature) obj;
		if (prodIds == null) {
			if (other.prodIds != null)
				return false;
		} else if (!prodIds.equals(other.prodIds))
			return false;
		return true;
	}

	/**
	 * A feature f1 is equivalent to f2 if they contains the same set of
	 * triggers
	 * @param f - the other feature
	 * @return if they are equivalent
	 */
	public boolean equivalent(Feature f) {
		if (this == f)
			return true;
		if (f == null) return false;
		return containsAll(f) && f.containsAll(this);
	}
	
	public Feature getEquivalent(Collection<Feature> list) {
		for (Feature e : list) {
			if (equivalent(e))
				return e;
		}
		return null;
	}
	
	/**
	 * Adds the products of f to the product list of this feature
	 * @param f
	 */
	private void mergeProducts(Feature f) {
		prodIds.addAll(f.prodIds);
	}
	
	/**
	 * If this feature contains all the triggers of f, removes those triggers
	 * and f adds the current product ids to its list
	 * @param f
	 * @return
	 */
	public Feature canonize(Feature f) {
		Collection<Trigger> intersec = intersection(f);
		if (intersec.isEmpty()) return null;
		removeAll(intersec);
		f.removeAll(intersec);
		Feature norm = new Feature(intersec);
		norm.prodIds.addAll(prodIds);
		norm.prodIds.addAll(f.prodIds);
		return norm;
	}
	
	private Collection<Trigger> intersection (Feature f) {
		ArrayList<Trigger> intersec = new ArrayList<Trigger>();
		for (Trigger t : this) {
			if (f.contains(t))
				intersec.add(t);
		}
		return intersec;
	}
	
	/**
	 * Merge the product list if the feature is equivalent to f
	 * @param f - the other feature
	 * @return
	 */
	public boolean mergeIfEquivalents(Feature f) {
		if (!equivalent(f)) return false;
		mergeProducts(f);
		return true;
	}
	
	/**
	 * Extracts the common triggers between a collection of triggers
	 * @param lines - a list of collection of triggers
	 * @return the common triggers
	 */
	public static Collection<Trigger> getCommonLine(List<? extends
			Collection<Trigger>> lines) {
		
		Collection<Trigger> common = new ArrayList<Trigger>();
		if (lines == null) return null;
		if (lines.size() == 0) return common;
		
		//Looking for the shortest line
		int size = lines.size();
		int minInd = 0;
		int minSize = lines.get(0).size();
		for (int i = 0; i < size; i++) {
			Collection<Trigger> line = lines.get(i);
			int curSize = line.size();
			if (curSize < minSize) {
				minSize = curSize;
				minInd = i;
			}
		}
		
		//Looking for the common line
		for (Trigger trigger : lines.get(minInd)) {
			boolean isCommon = true;
			for (int i=0; i<size; i++) {
				if (minInd != i) {
					if (!lines.get(i).contains(trigger)) {
						isCommon = false;
						break;
					}
				}
			}
			if (isCommon && !common.contains(trigger))
				common.add(trigger);
		}
		return common;
	}

}
